/**
 * @file boards\gd32f103cb\bsp\bsp_mobus_t35.c
 *
 * Copyright (C) 2023
 *
 * bsp_mobus_t35.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */

/*---------- includes ----------*/
#include "gd32f30x_libopt.h"
#include "options.h"
#include "timer.h"


/*---------- macro ----------*/
/*---------- type define ----------*/
/*---------- variable prototype ----------*/
/*---------- function prototype ----------*/
static bool bsp_init(void);
static void bsp_deinit(void);
static bool bsp_enable(bool ctrl);

/*---------- variable ----------*/
static timer_describe_t t35 = { .freq = 1000,
                               .ops.init = bsp_init,
                               .ops.deinit = bsp_deinit,
                               .ops.enable = bsp_enable,
                               .ops.irq_handler = NULL };
DEVICE_DEFINED(mobust35, timer, &t35);

/*---------- function ----------*/
static inline uint32_t _get_auto_reload(uint32_t clock_freq, uint32_t prescaler,
    uint32_t timer_freq) {
    uint32_t auto_reload = 0;

    if ((clock_freq / (prescaler + 1)) >= timer_freq) {
        auto_reload =
            (uint32_t)(((float)clock_freq / ((float)timer_freq * (prescaler + 1))) +
                0.5) -
            1;
    }

    return auto_reload;
}

static void bsp_timer_init(void) {
    timer_parameter_struct timer = { 0 };
    uint32_t timer_clock_freq = rcu_clock_freq_get(CK_APB1);

    rcu_periph_clock_enable(RCU_TIMER5);
    if ((RCU_CFG0 & RCU_CFG0_APB1PSC) != RCU_APB1_CKAHB_DIV1) {
        timer_clock_freq <<= 1;
    }
    timer_deinit(TIMER5);
    timer_struct_para_init(&timer);
    do {
        timer.period =
            _get_auto_reload(timer_clock_freq, timer.prescaler, t35.freq);
        if (timer.period < UINT16_MAX) {
            break;
        }
        timer.prescaler++;
    } while (1);
    timer_init(TIMER5, &timer);
    timer_single_pulse_mode_config(TIMER5, TIMER_SP_MODE_SINGLE);
    timer_interrupt_flag_clear(TIMER5, TIMER_INT_FLAG_UP);
    timer_update_event_enable(TIMER5);
    timer_interrupt_enable(TIMER5, TIMER_INT_UP);
}

static inline void bsp_timer_deinit(void) {
    timer_disable(TIMER5);
    timer_interrupt_disable(TIMER5, TIMER_INT_UP);
    timer_update_event_disable(TIMER5);
    timer_deinit(TIMER5);
    rcu_periph_clock_disable(RCU_TIMER5);
}

static inline void bsp_nvic_init(void) {
    NVIC_ClearPendingIRQ(TIMER5_IRQn);
    NVIC_SetPriority(TIMER5_IRQn, NVIC_PRIORITY_MAKE(CONFIG_PRIORITY_TIMER5));
    NVIC_EnableIRQ(TIMER5_IRQn);
}

static inline void bsp_nvic_deinit(void) {
    NVIC_DisableIRQ(TIMER5_IRQn);
    NVIC_ClearPendingIRQ(TIMER5_IRQn);
}

static bool bsp_init(void) {
    bsp_timer_init();
    bsp_nvic_init();

    return true;
}

static void bsp_deinit(void) {
    bsp_nvic_deinit();
    bsp_timer_deinit();
}

static bool bsp_enable(bool en) {
    if (en) {
        timer_counter_value_config(TIMER5, 0);
        timer_enable(TIMER5);
    }
    else {
        timer_disable(TIMER5);
    }

    return true;
}
